package me.pplay.playtalk.protocol;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Set;

import me.pplay.playtalk.protocol.bean.KeyValue;
import me.pplay.playtalk.protocol.bean.SendNode;
import me.pplay.playtalk.protocol.bean.TpPacket;
import me.pplay.playtalk.util.LogManager;
import android.util.Log;

public abstract class ProtocolPacket {
	public static final int TP_PACKET_HDRLEN = (2 + 2 + 4 + 2 + 2 + 2 + 2 + 4);
	public static final int TP_NEED_RECORD = 0x0001;
	private static final short TP_PACKET_VERISON = 0x0010;
	private static final short TP_PACKET_BUFFER = 1048 * 20;
	private static final int TP_SOCKET_SELECT_INTERVAL = 200;
	private static final int TP_KEEPALIVE_TICK = 30 * 1000;
	/* the flags we use while establishing a connection */

	/* the flags we use while tlv prpl for file transporting */
	public static final int TP_FILE_TLV = 0x0010;
	private byte[] readbuf = new byte[TP_PACKET_BUFFER];
	private short bm_read_index = 0;
	private short need_bytes = 0;
	private short got_bytes = 0;
	private int flag = 0;
	private TpPacket pkt = null;
	private long oldtime = 0;
	private long keeptime = 0;
	Socket socket = null;
	OutputStream ops = null;
	InputStream ips = null;
	private ProtocolList sendlist = null;
	private PacketThread thread = null;
	// private LinkedList<Map> hash=null;
	private String URL = "www.google.com.hk";
	private int PORT = 80;
	private boolean isTrue = false;

	public void setProtocolPacketSendList(ProtocolList sendlist) {
		this.sendlist = sendlist;
	}

	public ProtocolList getProtocolPacketSendList() {
		return this.sendlist;
	}

	public short getNeed_bytes() {
		return need_bytes;
	}

	public void setNeed_bytes(short need_bytes) {
		this.need_bytes = need_bytes;
	}

	private int tp_packet_write(Map<String, String> map, byte[] data, int dataoffset) {
		int pos = 0;
		Set<String> set = map.keySet();
		for (String key : set) {

			String value = map.get(key);
			byte[] keyb = key.getBytes();
			System.arraycopy(keyb, 0, data, pos + dataoffset, keyb.length);
			pos += keyb.length;
			data[dataoffset + pos++] = (byte) 0xc0;
			data[dataoffset + pos++] = (byte) 0x80;
			keyb = value.getBytes();
			System.arraycopy(keyb, 0, data, pos + dataoffset, keyb.length);
			pos += keyb.length;
			data[dataoffset + pos++] = (byte) 0xc0;
			data[dataoffset + pos++] = (byte) 0x80;
		}
		return pos;
	}

	protected void SET_TP_FLAG(int A) {
		this.flag |= A;
	}

	private void CLR_TP_FLAG(int A) {
		this.flag &= ~A;
	}

	private boolean IS_SET_TP_FLAG(int A) {
		return (this.flag & A) != 0 ? true : false;
	}

	private int tp_put(byte[] buf, int pos, int data) {
		buf[pos + 0] = (byte) ((byte) (data >> 24) & 0xff);
		buf[pos + 1] = (byte) ((byte) (data >> 16) & 0xff);
		buf[pos + 2] = (byte) ((byte) (data >> 8) & 0xff);
		buf[pos + 3] = (byte) ((byte) (data) & 0xff);
		return 4;
	}

	private int tp_put(byte[] buf, int pos, short data) {
		buf[pos] = (byte) ((byte) (data >> 8) & 0xff);
		buf[pos + 1] = (byte) ((byte) (data) & 0xff);
		return 2;
	}

	private short tp_get16(byte[] buf, int pos) {
		return (short) (((buf[0 + pos] << 8) & 0xff00) + ((buf[1 + pos]) & 0xff));
	}
	private int tp_get16_int(byte[] buf, int pos) {
		int p1 = (((buf[0 + pos] << 8) & 0xff00) + ((buf[1 + pos]) & 0xff));
		return p1;
	}
	private int tp_get32(byte[] buf, int pos) {
		return ((buf[0 + pos] << 24) & 0xff000000) + ((buf[1 + pos] << 16) & 0x00ff0000) + ((buf[2 + pos] << 8) & 0x0000ff00) + ((buf[3 + pos]) & 0x000000ff);
	}

	private void tp_packet_read(byte[] data, int len) {
		Map<String, String> hash = pkt.getHash();
		LinkedList<KeyValue> list = pkt.getList();
		int pos = 0;
		while (pos + 1 < len) {
			byte[] key = null;
			byte[] value = null;
			boolean accept;
			int x;

			key = new byte[len + 1];
			x = 0;
			while (pos + 1 < len) {
				if ((byte) data[pos] == (byte) 0xc0 && data[pos + 1] == (byte) 0x80)
					break;
				key[x++] = data[pos++];
			}
			key[x] = 0;
			pos += 2;

			// pair->key = atoi(key);
			// memcpy(pair->keystr,key,7);

			// free(key);

			accept = x != 0 ? true : false;
			/* if x is 0 there was no key, so don't accept it */
			if (accept) {
				value = new byte[len - pos + 1];
			}
			x = 0;
			while (pos + 1 < len) {
				if ((byte) data[pos] == (byte) 0xc0 && (byte) data[pos + 1] == (byte) 0x80)
					break;
				if (accept)
					value[x++] = data[pos++];
			}
			if (accept)
				value[x] = 0;
			pos += 2;
			if (accept) {
				KeyValue kv = new KeyValue();
				String keys = new String(key).trim();
				String values = new String(value).trim();
				kv.key = keys;
				kv.value = values;
				hash.put(keys, values);
				list.add(kv);
				// hash.put(new String(key).trim(), new String(value).trim());
				// hash.addLast(pair);
//				LogManager.LogShow("ProtocolPacket", "tp_packet_read len" + len
//						+"keys="+keys+" values="+values +" list.size = "+list.size());
			}
		}
	}

	int tp_packet_length(Map<String, String> map) {
		int len = 0;
		if (map == null)
			return len;
		Set<String> set = map.keySet();
		for (String key : set) {
			String value = map.get(key);
//			LogManager.LogShow("ProtocolPacket", "tp_packet_length key" + key + "    value:"+value);
			try {
				len += (key.getBytes("UTF-8").length + value.getBytes("UTF-8").length + 4);
			} catch (UnsupportedEncodingException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		return len;
	}

	public byte[] tp_packet_putdata(Map<String, String> map) {
		int len = tp_packet_length(map);
		byte[] data = new byte[len];
		tp_packet_write(map, data, 0);
		return data;
	}

	public boolean tp_send_socket(byte[] data) {
		try {
			ops.write(data);//
			ops.flush();//
		} catch (IOException e) {
			// TODO Auto-generated catch block
			tp_recvcb(ProtocolType.TP_ERRCODE_ERR_SOCKETSEND, null, null);
			e.printStackTrace();
			return false;
		}
		return true;
	}

	public boolean tp_send_packetex(TpPacket pkt, Map<String, String> map, int skyid, byte[] utf8data, int utf8len) {
		short pktlen = (short) (tp_packet_length(map) + utf8len);
		int pos = 0;
		int len = TP_PACKET_HDRLEN + pktlen;
		byte[] data = null;
		if (ops == null)
			return false;

		data = new byte[len];
		data[0] = 'T';
		data[1] = 'P';
		pos += 2;
		pos += tp_put(data, pos, TP_PACKET_VERISON);//
		pos += tp_put(data, pos, skyid);
		pos += tp_put(data, pos, pktlen);
		pos += tp_put(data, pos, pkt.getService());
		pos += tp_put(data, pos, (short)pkt.getSubid());
		pos += tp_put(data, pos, pkt.getStatus());
		pos += tp_put(data, pos, pkt.getId());
		// pos += tp_put32(data + pos, ++s_session_id);
		if (map != null)
			pos += tp_packet_write(map, data, pos);
		if (utf8data != null && utf8data.length > 0) {
			System.arraycopy(utf8data, 0, data, pos, utf8len);
		}
		// yahoo_packet_dump(data, len);
		//
		Log.v("ProtocolPacket", ">>>>>>>>SEND: pkt.getService" + pkt.getService() + " pkt.getSubid() = " + pkt.getSubid() + " pkt.getStatus() = " + pkt.getStatus() + " getId() = " + pkt.getId() + " skyid() = " + skyid + " data = " + new String(data));
//		Log.v("ProtocolPacket",UserInfo.bytesAsHexString(data,256));
		try {
			ops.write(data);//
			ops.flush();//
		} catch (IOException e) {
			// TODO Auto-generated catch block
			tp_recvcb(ProtocolType.TP_ERRCODE_ERR_SOCKETSEND, null, null);
			e.printStackTrace();
			return false;
		}
		return true;
		// LOG((">>>>>>>>SEND:serverid=0x%x,subid=0x%x,s_session_id=%d\n%s",pkt->service,pkt->subid,pkt->id,data
		// + pos));
	}

	public abstract void tp_recvcb(int resulut, TpPacket pkt, SendNode node);

	public abstract void tp_keeplive();

	public abstract boolean tp_sendcb(int cmd, SendNode nodedata);

	public void tp_recv() {
		short pos = 0;
		int read_len = 0;// , is_client = IS_SET_SSL_FLAG(SSL_IS_CLIENT);
		byte[] buf = this.readbuf;
		if (ips == null)
			return;
		// LOG(("----- tp_Recv -----this.got_bytes = %d,this.need_bytes = %d",this.got_bytes,this.need_bytes));
		try {
			// if (ips.available() == -1)
			// return;
			int result = ips.available();
			if (result == 0) {
				return;
			}
			
			read_len = ips.read(this.readbuf, this.bm_read_index, this.need_bytes - this.got_bytes);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			tp_recvcb(ProtocolType.TP_ERRCODE_ERR_SOCKETRECV, null, null);
			this.bm_read_index = 0;
			e.printStackTrace();
			return;
		}
		/* connection has gone, so die */
		// LOG(("----- tp_Recv -----read_len = %d",read_len));
		if (read_len <= 0)
			return;

		this.got_bytes += read_len;
		this.bm_read_index += read_len;
		// LOG(("----- yahoo_Recv -----this.got_bytes = %d,this.need_bytes = %d",this.got_bytes,this.need_bytes));
		/* haven't quite got what we want, so try again later */
		if (this.got_bytes < this.need_bytes)
			return;

		if (IS_SET_TP_FLAG(TP_FILE_TLV)) {
			int len_value = 0;
 
			read_len = this.got_bytes;
			if (read_len < 8)
				return;
			len_value = tp_get32(buf, 4);
			if (len_value + 8 != this.need_bytes)
				this.need_bytes = (short) (len_value + 8);
			if (len_value + 8 > read_len)
				return;

			this.got_bytes = 0;
			this.need_bytes = 8;

			{ 
				TpPacket pkttmp = new TpPacket();
				pkttmp.setUploadData(buf);
				tp_recvcb(ProtocolType.TP_ERRCODE_OK, pkttmp, null);
				SendNode node = sendlist.tp_data_firstnode();

				if (node != null && node.getTimeout() > 0 && node.isSent()) {
					sendlist.tp_data_remove_send(node.getCmd());
				}
			}
			this.pkt = null;
		} else {
			read_len = this.got_bytes;
			this.got_bytes = 0;
			if (IS_SET_TP_FLAG(TP_NEED_RECORD)) {
				if (buf[0] == 'T' && buf[1] == 'P') {
					short ver;
					int skyid;
					// int pos=0;
					pos += 2; /* TP */
					ver = tp_get16(buf, pos);
					pos += 2;/* ver */
					if (ver != TP_PACKET_VERISON) {
						tp_recvcb(ProtocolType.TP_ERRCODE_ERR_TPVERSION, null, null);
						this.bm_read_index = 0;
						return;
					}
					skyid = tp_get32(buf, pos);
					pos += 4;/* skyid */ 
					this.need_bytes = tp_get16(buf, pos);
					pos += 2;/* pkt_len */
					CLR_TP_FLAG(TP_NEED_RECORD);

					TpPacket pkt = new TpPacket();

					pkt.setService(tp_get16(buf, pos));
					pos += 2; 
					pkt.setSubid(tp_get16_int(buf, pos));
					pos += 2;
					pkt.setStatus(tp_get16(buf, pos));
					pos += 2;
					pkt.setId(tp_get32(buf, pos));
					pos += 4;
					pkt.setHash(new HashMap<String, String>());
					pkt.setList(new LinkedList<KeyValue>());//(new HashMap<String, String>());
					LogManager.LogShow("ProtocolPacket", "<<<<<<<<RECV: pkt.getService" + pkt.getService() + " pkt.getSubid() = " + pkt.getSubid() + " pkt.getStatus() = " + pkt.getStatus() + " getId() = " + pkt.getId() + " skyid() = " + skyid );
					// LOG(("_Recv%x,0x%x,%d,%d",pkt.service,pkt.status,pkt.id,this.need_bytes));
					this.pkt = pkt;
					if (this.need_bytes > 0) {
						this.bm_read_index = 0;
						return;
					}

				} else {
					/*
					 * HEY! This isn't even a YMSG packet. What are you trying
					 * to pull?
					 */
					tp_recvcb(ProtocolType.TP_ERRCODE_ERR_SOCKETDATAERR, null, null);
					this.bm_read_index = 0;
					return;
					// goto error;
				}
			}
			/* for next time - just do it now in case of an error */
			tp_packet_read(buf, this.need_bytes);
			SET_TP_FLAG(TP_NEED_RECORD);
			{
				LogManager.LogShow("ProtocolPacket", "<<<<<<<<RECV" + new String(buf));
				buf[0] = 0;
			}
			this.need_bytes = TP_PACKET_HDRLEN;
			
			/* The main part of the yahoo packet */
			// tp_packet_process(yd, pkt);
			// LOG(("<<<<<<<<RECV:serverid=0x%x,subid=0x%x,s_session_id=%d\n%s",pkt->service,pkt->subid,pkt->id,buf
			// + pos));
			// ����յ�����Ҫ�ȴ��sessionid����ɾ���ָ��

			SendNode node = sendlist.tp_data_firstnode();
			if (node != null && pkt.getId() == node.getSession_id() && node.getTimeout() > 0 && node.isSent()) {
				boolean flag = sendlist.tp_data_remove_send(node.getCmd());
				LogManager.LogShow("ProtocolPacket", "<<<<<<<<DEL CMD " + node.getCmd()+"remove is " + flag);
			}
			// pkt->send = node;

			tp_recvcb(ProtocolType.TP_ERRCODE_OK, pkt, null);
			this.bm_read_index = 0;

			this.pkt = null;
		}
		this.bm_read_index = 0; /* reset to go again */
		return;
	}

	public class PacketThread extends Thread {
		public void run() {
			try {
				socket = new Socket(URL, PORT);
				ops = socket.getOutputStream();//
				ips = socket.getInputStream();//
			} catch (UnknownHostException e) {
				e.printStackTrace();
				Log.v("ProtocolPacket", "UnknownHostException resulut = ");
				return; 
			} catch (IOException e) {
				e.printStackTrace();
				Log.v("ProtocolPacket", "IOException resulut = ");
				return;
			} 
			System.out.println("\t>>>>>> Socket End");
			while (isTrue) {
				try {
					Thread.sleep(50);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				tp_recv();
				tp_data_sendcycle();
				if(keeptime >= TP_KEEPALIVE_TICK/50){
					tp_keeplive();
					keeptime = 0;
				}
				keeptime++;
//				if (curtime - oldtime >= TP_SOCKET_SELECT_INTERVAL) {
//					tp_data_sendcycle();
//					oldtime = curtime;
//				} 
			}
		}
	}

	public void tp_data_starturl(String url, int port) {
		URL = url;
		PORT = port;
		Log.v("ProtocolPacket", "tp_data_starturl" + url + port);
		if (thread != null)
			thread.interrupt();
		thread = null;
		if (thread == null) {
			thread = new PacketThread();
			isTrue = true;
			thread.start();
		}
	}

	public void tp_data_stop() {
		
		if (isTrue) {
			thread.interrupt();
			thread = null;
			try {
				ops.close();
				ips.close();
				socket.close();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				tp_recvcb(ProtocolType.TP_ERRCODE_ERR_SOCKET, null, null);
				e.printStackTrace();
			}
		}
		isTrue = false;
	}

	private void tp_data_sendcycle() {
		if (sendlist == null)
			return;
		SendNode node = sendlist.tp_data_firstnode();
		if (socket.isConnected() && node != null) {
			//
			if (!node.isSent()) {
				boolean sent = tp_sendcb(node.getCmd(), node);
				node.setSent(sent);
			}

			if (node.isSent()) {
				if (node.getTimeout() <= 0) {
					sendlist.tp_data_remove_send(node.getCmd());
				} else {
					int timeout = node.getTimeout();
					timeout--;
					node.setTimeout(timeout--);
					if (node.getTimeout() == 0) {
						tp_recvcb(ProtocolType.TP_ERRCODE_ERR_TIMEOUT, null, node);
						sendlist.tp_data_remove_send(node.getCmd());
					} 
				}
			}
		}
	}

}
